var BUFFER_SIZE = 8192;

QUnit.test("createSocket_", function(assert) {
  var client = new FTPClient(MockSocket);
  var	createSocket_ = client.test.createSocket_, FTPInfo = client.test.FTPInfo;
  QUnit.stop();
  createSocket_().then(function(socketInfo){
    QUnit.ok(socketInfo.socketId >= 0, "socket created");
  }).catch(function (error) {
  QUnit.equal(error, "This should have succeeded");
  });
  QUnit.start();
});

QUnit.test("connectWrapper_", function() {
	QUnit.stop();
  var client = new FTPClient(MockSocket),
    createSocket_ = client.test.createSocket_,
    connectWrapper_ = client.test.connectWrapper_;
	expect(5);
  createSocket_().then(function (socketInfo) {
    return connectWrapper_(socketInfo.socketId, "cmu.edu", 22);
  }).then(function (result) {
    QUnit.ok(result >= 0, "connection successful");
	}).catch(function (error) {
		QUnit.equal(error, "This should have suceeded");
	});
	createSocket_().then(function (socketInfo) {
		return connectWrapper_(socketInfo.socketId, "", 22);
  }).then(function (result) {
    QUnit.equal(result, "connection should not succeed");
	}).catch(function (error) {
		QUnit.ok(error instanceof Error === true, "error received");
    QUnit.equal(error.message, "Error code -9",
			"connection fail expected");
	});
	createSocket_().then(function (socketInfo) {
		return connectWrapper_(socketInfo.socketId, "cmu.edu", NaN);
	}).then(function (result) {
		QUnit.equal(result, "connection should not succeed");
	}).catch(function (error) {
		QUnit.ok(error instanceof Error === true, "error received");
    QUnit.equal(error.message, "Error code -9",
      "connection fail expected");
	});
	QUnit.start();
});

QUnit.test("connect", function() {
	expect(5);
	QUnit.stop();
	var client = new FTPClient(MockSocket);
	client.connect("google.com", 22).then(function (reply) {
    QUnit.equal(reply, "220 Service ready for new user\r\n",
      "connection successful");
	}).catch(function(error){
		QUnit.equal(error, "This should have suceeded");
	});
	client = new FTPClient(MockSocket);
	client.connect("", 22).then(function (reply) {
		QUnit.equal(reply, "Connection should not succeed");
	}).catch(function (error){
    QUnit.ok(error instanceof Error === true, "error received");
		QUnit.equal(error.message, "Error code -9",
      "Connection fail expected");
	});
	client = new FTPClient(MockSocket);
	client.connect("cmu.edu","portNaN").then(function (reply) {
		QUnit.equal(reply, "Connection should not succeed");
	}).catch(function (error) {
		QUnit.ok(error instanceof Error === true, "error received");
		QUnit.equal(error.message, "Error code -9",
      "Connection fail expected");
		QUnit.start();
	});
});

QUnit.test("splitLines_", function() {
	expect(27);
	// case I
	var client = new FTPClient(MockSocket);
	var FTPInfo = client.test.FTPInfo, splitLines_ = client.test.splitLines_,
		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");

	var receivedData = encoder.encode("rnr\nA\r\n");
	splitLines_(receivedData);
	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE), "no leftOver");
	QUnit.equal(commandRawData.buffOffset, 0, "buffOffset 0 - no leftOver");
	QUnit.deepEqual(commandRawData.parsedLines, ["rnr\n", "A\r\n"], "parsedLines");

	// case II
	receivedData = encoder.encode("google ");
	splitLines_(receivedData);
	QUnit.deepEqual(commandRawData.buffer.subarray(0, receivedData.byteLength),
    receivedData, "leftOvers");
	QUnit.equal(commandRawData.buffOffset, receivedData.byteLength);

	// case III
	receivedData = encoder.encode("love\r\n");
	splitLines_(receivedData);
	QUnit.deepEqual(commandRawData.parsedLines, ["rnr\n", "A\r\n", "google love\r\n"],
    "added new parsed line");
	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE),
    "reset buffer after combining");
	QUnit.equal(commandRawData.buffOffset, 0, "reset buffOffset after combining");

	// case Iv
	receivedData = new Uint8Array([230, 176]);
	splitLines_(receivedData);
	QUnit.deepEqual(commandRawData.buffer.subarray(0, receivedData.byteLength),
    receivedData, "wild character split");
	QUnit.equal(commandRawData.buffOffset, receivedData.byteLength);

	// case V
	receivedData = new Uint8Array([180, 32, 119, 105, 108, 100, 13, 10, 116]);
	splitLines_(receivedData);
	QUnit.deepEqual(commandRawData.buffer.subarray(0,1), new Uint8Array([116]));
	QUnit.deepEqual(commandRawData.parsedLines, ["rnr\n", "A\r\n", "google love\r\n",
    "水 wild\r\n"]);
	QUnit.equal(commandRawData.buffOffset, 1, "combined data, rest buffOffset");

	//case VI
	receivedData = new Uint8Array([111, 98, 101]);
	splitLines_(receivedData);
	QUnit.deepEqual(commandRawData.buffer.subarray(0, commandRawData.buffOffset),
    new Uint8Array([116, 111, 98, 101]), "combined non line data");
  client = new FTPClient(MockSocket);
	FTPInfo = client.test.FTPInfo;
  splitLines_ = client.test.splitLines_;
  commandRawData = FTPInfo.commandRawData;
	receivedData = encoder.encode("320 google\r\n");
	splitLines_(receivedData);
	QUnit.deepEqual(commandRawData.parsedLines, ["320 google\r\n"], "basic test");
	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE), "no left over");
	QUnit.equal(commandRawData.buffOffset, 0, "buffOffset remained 0");

	var receivedDataI = encoder.encode("320");
	splitLines_(receivedDataI);
	QUnit.deepEqual(commandRawData.parsedLines, ["320 google\r\n"], "uncomsumed line");
	QUnit.deepEqual(commandRawData.buffer.subarray(0, commandRawData.buffOffset), receivedDataI,
    "data stored");
	QUnit.equal(commandRawData.buffOffset, receivedDataI.byteLength,
    "buffOffset reflects dataReceived");

	var receivedDataII = encoder.encode(" goog");
  splitLines_(receivedDataII);
  QUnit.deepEqual(commandRawData.parsedLines, ["320 google\r\n"], "unconsumed line");
  QUnit.deepEqual(commandRawData.buffer.subarray(0, receivedDataI.byteLength), receivedDataI,
    "receivedDataI stored");
	QUnit.deepEqual(commandRawData.buffer.subarray(receivedDataI.byteLength, commandRawData.buffOffset),
    receivedDataII, "receivedDataII stored");
	QUnit.equal(commandRawData.buffOffset, receivedDataI.byteLength + receivedDataII.byteLength,
    "buffOffset reflects dataReceived");

	var receivedDataIII = encoder.encode("le\r\n");
	splitLines_(receivedDataIII);
	QUnit.deepEqual(commandRawData.parsedLines, ["320 google\r\n", "320 google\r\n"],
    "unconsumed lines");
  QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE),
    "got full line, buffer now empty");
	QUnit.equal(commandRawData.buffOffset, 0, "buffOffset reset");
});

QUnit.test("getReply_ multilined", function() {
	expect(22);
	var client = new FTPClient(MockSocket);
	var FTPInfo = client.test.FTPInfo, getReply_ = client.test.getReply_,
		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
	var receivedData = encoder.encode("320-google\r\n");
	var responses = getReply_(receivedData);
	QUnit.deepEqual(commandRawData.parsedLines, ["320-google\r\n"], "basic test");
	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE), "no leftOver");
	QUnit.equal(commandRawData.buffOffset, 0, "buffOffset = 0");
  QUnit.ok(responses === null, "No response received yet");

	var receivedDataI = encoder.encode("love ");
	responses = getReply_(receivedDataI);
	QUnit.deepEqual(commandRawData.parsedLines, ["320-google\r\n"], "no new line received");
	QUnit.deepEqual(commandRawData.buffer.subarray(0, commandRawData.buffOffset), receivedDataI,
    "accumulating data");
	QUnit.equal(commandRawData.buffOffset, receivedDataI.byteLength,
    "updated buffOffset");
	QUnit.ok(responses === null, "No response received yet");

	var receivedDataII = encoder.encode("live ");
	responses = getReply_(receivedDataII);
	QUnit.deepEqual(commandRawData.parsedLines, ["320-google\r\n"], "again no new line");
	QUnit.deepEqual(commandRawData.buffer.subarray(0, receivedDataI.byteLength),
    receivedDataI, "previous data is still in the buff");
	QUnit.deepEqual(commandRawData.buffer.subarray(receivedDataI.byteLength,
    receivedDataI.byteLength + receivedDataII.byteLength), receivedDataII,
    "accumulated new data");
	QUnit.equal(commandRawData.buffOffset, receivedDataI.byteLength+receivedDataII.byteLength,
    "updated buffOffset");
	QUnit.ok(responses === null, "No response received yet");

	var receivedDataIII = encoder.encode("laugh\r\n");
	responses = getReply_(receivedDataIII);
	QUnit.deepEqual(commandRawData.parsedLines, ["320-google\r\n", "love live laugh\r\n"],
    "combined replies");
	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE), "reset buffer");
	QUnit.equal(commandRawData.buffOffset, 0, "reset buffOffset");
	QUnit.ok(responses === null, "No response received yet");

	var receivedDataIV = encoder.encode("320");
	responses = getReply_(receivedDataIV);
	QUnit.ok(responses === null, "No response just yet");
	var receivedDataV = encoder.encode(" \r\n");
	responses = getReply_(receivedDataV);
	QUnit.deepEqual(responses, ["320-google\r\nlove live laugh\r\n320 \r\n"],
    "full response received");
	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE),
    "buffer cleared");
	QUnit.equal(commandRawData.buffOffset, 0, "bufferOffsert reset");
	QUnit.deepEqual(commandRawData.parsedLines, [], "parsed lines cleared");
});

QUnit.test("getReply_ singlelined", function() {
	expect(8);
	var client = new FTPClient(MockSocket);
	var FTPInfo = client.test.FTPInfo, getReply_ = client.test.getReply_,
		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
	var receivedData = encoder.encode("320 google");
	var responses = getReply_(receivedData);
	QUnit.deepEqual(commandRawData.parsedLines, [], "basic test");
	QUnit.deepEqual(commandRawData.buffer.subarray(0, receivedData.byteLength), receivedData,
    "no new lines");
	QUnit.equal(commandRawData.buffOffset, receivedData.byteLength, "buffOffset updated");
	QUnit.ok(responses === null, "No response received yet");

	var receivedDataI = encoder.encode("\r\n");
	responses = getReply_(receivedDataI);
	QUnit.deepEqual(commandRawData.parsedLines, [], "response returned");
	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE));
	QUnit.deepEqual(responses, ["320 google\r\n"]);
	QUnit.equal(commandRawData.buffOffset, 0);
});

QUnit.test("getReply_ singlelined & multilined", function() {
	expect(17);
	var client = new FTPClient(MockSocket);
	var FTPInfo = client.test.FTPInfo, getReply_ = client.test.getReply_,
		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
	var receivedData = encoder.encode("320 google");
	var responses = getReply_(receivedData);
	QUnit.ok(responses === null, "No response received yet");
	var receivedDataI = encoder.encode("\r\n320-google");
	responses = getReply_(receivedDataI);
	var leftOver = encoder.encode("320-google");
	QUnit.deepEqual(responses, ["320 google\r\n"],
    "The singlelined response received");
	QUnit.deepEqual(commandRawData.parsedLines, [], "no parsed lines");
	QUnit.deepEqual(commandRawData.buffer.subarray(0, leftOver.byteLength), leftOver,
    "leftOver was stored in buff");
	QUnit.equal(commandRawData.buffOffset, leftOver.byteLength, "buffOffset updated");

	var receivedDataII = encoder.encode("\r\nnext");
	responses = getReply_(receivedDataII);
	QUnit.ok(responses === null, "No new response received");
	QUnit.deepEqual(commandRawData.parsedLines, ["320-google\r\n"],
    "first line of multiline response");
	leftOver = encoder.encode("next");
	QUnit.deepEqual(commandRawData.buffer.subarray(0, leftOver.byteLength), leftOver,
    "leftOver was stored in buff");
	QUnit.equal(commandRawData.buffOffset, leftOver.byteLength, "buffOffset updated");

	var receivedDataIII = encoder.encode("\r\n320 \r\n");
	responses = getReply_(receivedDataIII);
	QUnit.deepEqual(commandRawData.parsedLines, [], "received full multi-lined response");
	QUnit.deepEqual(responses, ["320-google\r\nnext\r\n320 \r\n"], "response");
	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE), "buffer cleared");
	QUnit.equal(commandRawData.buffOffset, 0, "buffOffset reset");

	var receivedDataIV = encoder.encode("320-multi\r\n320 \r\n320 singlelined\r\n");
	responses = getReply_(receivedDataIV);
	QUnit.deepEqual(commandRawData.parsedLines, [], "recieved response, no parsed lines");
	QUnit.deepEqual(responses, ["320-multi\r\n320 \r\n", "320 singlelined\r\n"],
    "multiple responses received");
	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE), "buffer cleared");
	QUnit.equal(commandRawData.buffOffset, 0 , "buffOffset reset");
});

QUnit.test("isLegalCode_", function () {
	var client = new FTPClient(MockSocket);
	var isLegalCode_ = client.test.isLegalCode_;
	QUnit.equal(isLegalCode_(""), false);
	QUnit.equal(isLegalCode_("01"), false);
	QUnit.equal(isLegalCode_("1w3"), false);
	QUnit.equal(isLegalCode_("abc"), false);
	QUnit.equal(isLegalCode_("012"), false);
	QUnit.equal(isLegalCode_("123"), true);
	QUnit.equal(isLegalCode_("hellothere"), false);
	QUnit.equal(isLegalCode_("000"), false);
});

QUnit.test("getReplyCode_", function () {
	var client = new FTPClient(MockSocket);
	var getReplyCode_ = client.test.getReplyCode_;
	QUnit.equal(getReplyCode_(""), undefined);
	QUnit.equal(getReplyCode_("12\r\n"), undefined);
	QUnit.equal(getReplyCode_("230-google\r\n"), 230);
	QUnit.equal(getReplyCode_("09"), undefined);
	QUnit.equal(getReplyCode_("099"), undefined);
	QUnit.equal(getReplyCode_("99"), undefined);
	QUnit.equal(getReplyCode_("  8"), undefined);
	QUnit.equal(getReplyCode_(" no digits here\r\n"), undefined);
	QUnit.equal(getReplyCode_("999 \r\n"), 999);
	QUnit.equal(getReplyCode_("10000 it's not a reply code"), undefined);
});

QUnit.test("onReceiveCallback_", function () {
	expect(17);
	var client = new FTPClient(MockSocket);
	var FTPInfo = client.test.FTPInfo,
    onReceiveCallback_ = client.test.onReceiveCallback_,
    commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
	QUnit.stop();
	client.connect("cmu.edu", 22).then(function () {
		QUnit.deepEqual(commandRawData.parsedLines, [], "reply consumed");
		QUnit.deepEqual(FTPInfo.serverResponses, [], "reply consumed");

		var commandId = FTPInfo.commandId;
		var sendData = encoder.encode("220 OK\r\n220-multi").buffer;
		var sendInfo = {socketId: commandId, data: sendData};

		onReceiveCallback_(sendInfo);

		var leftOver = encoder.encode("220-multi");
		QUnit.deepEqual(FTPInfo.serverResponses, ["220 OK\r\n"],
      "recorded server response");
		QUnit.deepEqual(commandRawData.parsedLines, [],
      "returned reply, parsedLines empty");
		QUnit.deepEqual(commandRawData.buffer.subarray(0, leftOver.byteLength),
      leftOver, "leftOver stored in buffer");
		QUnit.deepEqual(commandRawData.buffOffset, leftOver.byteLength);
		
		sendInfo.data = encoder.encode("line\r\n").buffer;
		onReceiveCallback_(sendInfo);
		QUnit.deepEqual(FTPInfo.serverResponses, ["220 OK\r\n"],
      "complete response has not been received yet");
		QUnit.deepEqual(commandRawData.parsedLines, ["220-multiline\r\n"],
      "begining of multiline");
		QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE),
      "new line recieved, buffer cleared");
		QUnit.equal(commandRawData.buffOffset, 0, "buffOffset reset");

		sendInfo.data = encoder.encode("220 end\r\n").buffer;
		onReceiveCallback_(sendInfo);
		QUnit.deepEqual(FTPInfo.serverResponses,
      ["220 OK\r\n", "220-multiline\r\n220 end\r\n"],
      "new response received");
		QUnit.deepEqual(commandRawData.parsedLines, [],
      "full responses received, no parsed lines");
		QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE),
      "new line recieved, buffer cleared");
		QUnit.equal(commandRawData.buffOffset, 0, "buffOffset reset");
		
		var responsePromise = client.test.readReply_();
		var responsePromiseI = client.test.readReply_();
		responsePromise.then(function (reply) {
      QUnit.equal(reply, "220 OK\r\n", "read single-lined response");
    });
    responsePromiseI.then(function (reply) {
      QUnit.equal(reply, "220-multiline\r\n220 end\r\n",
        "read multilined response");
		});
		QUnit.deepEqual(FTPInfo.serverResponses, [], "serverResponses cleared");
		QUnit.start();
	}).catch(function (error) {
		console.log("INSIDE TEST ERROR:", error);
		QUnit.equal(error, "This test should have succeeded");
		QUnit.start();
	});
});

QUnit.test("onReceiveCallback_ preliminary Responses basic", function () {
	expect(10);
	var client = new FTPClient(MockSocket);
	var FTPInfo = client.test.FTPInfo,
    onReceiveCallback_ = client.test.onReceiveCallback_,
    readReply_ = client.test.readReply_,
    readFinalReply_ = client.test.readFinalReply_,
		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
	QUnit.stop();
	client.connect("cmu.edu", 22).then(function () {
		QUnit.deepEqual(commandRawData.parsedLines, [], "reply consumed");
		QUnit.deepEqual(FTPInfo.serverResponses, [], "reply consumed");

		var commandId = FTPInfo.commandId;
		var sendData = encoder.encode("150 Sending data\r\n").buffer;
		var sendInfo = {socketId: commandId, data: sendData};

		onReceiveCallback_(sendInfo);
		sendInfo.data = encoder.encode("226 Closing data connection\r\n");
		onReceiveCallback_(sendInfo);
		QUnit.deepEqual(FTPInfo.serverResponses, ["150 Sending data\r\n",
      "226 Closing data connection\r\n"],
      "got 1yz server response");
		QUnit.deepEqual(commandRawData.parsedLines, [],
      "returned reply, parsedLines empty");
		var response = readReply_("LIST");
		response.then(function () {
			QUnit.equal(FTPInfo.preliminaryState, true, "in preliminary state");
			return readFinalReply_();
		}).then(function (reply) {
			QUnit.equal(reply, "226 Closing data connection\r\n", "read final response");
			QUnit.equal(FTPInfo.preliminaryState, false);
			QUnit.deepEqual(FTPInfo.preliminaryRead, null);
			QUnit.deepEqual(FTPInfo.serverResponses, [], "consumed server responses");
			QUnit.deepEqual(commandRawData.parsedLines, [], "no parsed lines");
			QUnit.start();
		});
	});
});

QUnit.test("onReceiveCallback_ preliminary responses", function () {
	expect(9);
	var client = new FTPClient(MockSocket);
	var FTPInfo = client.test.FTPInfo,
    onReceiveCallback_ = client.test.onReceiveCallback_,
    readReply_ = client.test.readReply_,
		readFinalReply_ = client.test.readFinalReply_,
		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
	QUnit.stop();
	client.connect("cmu.edu", 22).then(function () {
		QUnit.deepEqual(commandRawData.parsedLines, [], "reply consumed");
		QUnit.deepEqual(FTPInfo.serverResponses, [], "reply consumed");

		var commandId = FTPInfo.commandId;
		var sendData = encoder.encode("150 Sending data\r\n").buffer;
		var sendInfo = {socketId: commandId, data: sendData};

		onReceiveCallback_(sendInfo);
		var responseI = readReply_();
		var responseII = readReply_();

		sendInfo.data = encoder.encode("226 Closing data connection\r\n");
		onReceiveCallback_(sendInfo);
		
		sendInfo.data = encoder.encode("220 OK\r\n");
		onReceiveCallback_(sendInfo);
		
		responseI.then(function (reply) {
			QUnit.equal(reply, "150 Sending data\r\n");
			QUnit.equal(FTPInfo.preliminaryState, true, "entered preliminary state");
			QUnit.equal(FTPInfo.pendingReads.length, 1, "one pending read");
			return readFinalReply_();
		}).then(function (reply) {
			QUnit.equal(reply, "226 Closing data connection\r\n");
			QUnit.deepEqual(FTPInfo.pendingReads, [], "all reads resolved");
			QUnit.deepEqual(FTPInfo.serverResponses, [], "consumed all responses");
			responseII.then(function (reply) {
				QUnit.equal(reply, "220 OK\r\n");
				QUnit.start();
			}).catch(function (error) {
				throw error;
			});
		}).catch(function (error) {
			QUnit.equal(error, "This function should have suceeded!");
			QUnit.start();
		});
	});
});

QUnit.test("onReceiveCallback_ preliminary responses", function () {
	//expect(9);
	var client = new FTPClient(MockSocket);
	var FTPInfo = client.test.FTPInfo,
    onReceiveCallback_ = client.test.onReceiveCallback_,
    readReply_ = client.test.readReply_,
		readFinalReply_ = client.test.readFinalReply_,
		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
	QUnit.stop();
	client.connect("cmu.edu", 22).then(function () {
		QUnit.deepEqual(commandRawData.parsedLines, [], "reply consumed");
		QUnit.deepEqual(FTPInfo.serverResponses, [], "reply consumed");

		var commandId = FTPInfo.commandId;
		var sendData = encoder.encode("220 OK\r\n220 OK\r\n150 Sending data\r\n").buffer;
		var sendInfo = {socketId: commandId, data: sendData};
		
		readReply_();
		readReply_();
		QUnit.deepEqual(FTPInfo.pendingReads.length, 2, "two pending reads");
		QUnit.deepEqual(FTPInfo.serverResponses, [], "no responses received yet");
		
		onReceiveCallback_(sendInfo);
		QUnit.deepEqual(FTPInfo.pendingReads.length, 0, "pending reads resolved");
		QUnit.deepEqual(FTPInfo.serverResponses, ["150 Sending data\r\n"]);
		
		sendInfo.data = encoder.encode("226 Closing data connection\r\n220 OK\r\n");
		onReceiveCallback_(sendInfo);
		QUnit.deepEqual(FTPInfo.preliminaryState, false,
      "haven't entered preliminary state yet");
		QUnit.deepEqual(FTPInfo.serverResponses, ["150 Sending data\r\n",
      "226 Closing data connection\r\n", "220 OK\r\n"]);
		readReply_();
		QUnit.equal(FTPInfo.preliminaryState, true,
      "preliminary state encountered");
    readReply_();
		QUnit.equal(FTPInfo.pendingReads.length, 1,
      "pending reads but preliminary state");
		QUnit.deepEqual(FTPInfo.serverResponses, ["226 Closing data connection\r\n",
      "220 OK\r\n"]);
		readFinalReply_();
		QUnit.deepEqual(FTPInfo.serverResponses, [], "all responses consumed");
		QUnit.deepEqual(FTPInfo.preliminaryState, false, "preliminary state finished");
		QUnit.equal(FTPInfo.preliminaryRead, null, "final read returned");
		QUnit.start();
		}).catch(function (error) {
			QUnit.equal(error, "This function should have suceeded!");
			QUnit.start();
	});
});

QUnit.test("onReceiveCallback_ multiple preliminary responses error", function () {
	expect(6);
	var client = new FTPClient(MockSocket);
	var FTPInfo = client.test.FTPInfo,
    onReceiveCallback_ = client.test.onReceiveCallback_,
    readReply_ = client.test.readReply_,
		readFinalReply_ = client.test.readFinalReply_,
		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
	QUnit.stop();
	client.connect("cmu.edu", 22).then(function () {
		QUnit.deepEqual(commandRawData.parsedLines, [], "reply consumed");
		QUnit.deepEqual(FTPInfo.serverResponses, [], "reply consumed");

		var commandId = FTPInfo.commandId;
		var sendData = encoder.encode("150 Sending data\r\n150 Sending data\r\n").buffer;
		var sendInfo = {socketId: commandId, data: sendData};
		
		onReceiveCallback_(sendInfo);
		readReply_();

		QUnit.deepEqual(FTPInfo.serverResponses, ["150 Sending data\r\n"],
      "unconsumed server responses");
		readFinalReply_().catch(function (error) {
			QUnit.equal(error.message, "FTP protocol violation error",
        "multiple 1yz replies caught");
			QUnit.equal(FTPInfo.preliminaryState, false, "reset preliminary state");
			QUnit.equal(FTPInfo.preliminaryRead, null, "reset preliminary read");
			QUnit.start();
		})
  });
});

QUnit.test("onReceiveCallback_ preliminary responses error", function () {
	expect(6);
	var client = new FTPClient(MockSocket);
	var FTPInfo = client.test.FTPInfo,
    onReceiveCallback_ = client.test.onReceiveCallback_,
    readReply_ = client.test.readReply_,
		readFinalReply_ = client.test.readFinalReply_,
		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
	QUnit.stop();
	client.connect("cmu.edu", 22).then(function () {
		QUnit.deepEqual(commandRawData.parsedLines, [], "reply consumed");
		QUnit.deepEqual(FTPInfo.serverResponses, [], "reply consumed");

		var commandId = FTPInfo.commandId;
		var sendData = encoder.encode("220 OK\r\n220 OK\r\n").buffer;
		var sendInfo = {socketId: commandId, data: sendData};
		
		onReceiveCallback_(sendInfo);
		QUnit.deepEqual(FTPInfo.serverResponses, ["220 OK\r\n", "220 OK\r\n"],
      "unconsumed server responses");
		readFinalReply_().catch(function (error) {
			QUnit.equal(error.message,
        "Final read requested in non-preliminary state");
			QUnit.equal(FTPInfo.preliminaryState, false, "reset preliminary state");
			QUnit.equal(FTPInfo.preliminaryRead, null, "reset preliminary read");
			QUnit.start();
		})
  });
});


QUnit.test("sendCommand_", function (assert) {
	expect(10);
	QUnit.stop();
	var client = new FTPClient(MockSocket);
	var FTPInfo = client.test.FTPInfo,
		sendCommand_ = client.test.sendCommand_;
	client.connect("cmu.edu", 21).then(function (reply) {
		QUnit.deepEqual(reply, "220 Service ready for new user\r\n",
      "connection successful");
		return sendCommand_("USER anonymous\r\n");
	}).then(function (reply) {
    QUnit.equal(reply, "331 User name okay, need password\r\n",
      "got USER reply");
		return sendCommand_("USER\r\n");
	}).catch(function (error) {
		QUnit.equal(error.message, "501 Syntax error in parameters or arguments\r\n",
									"USER syntax error");
		return sendCommand_("INVALID\r\n");
	}).catch(function (error) {
		QUnit.equal(error.message, "500 Unknown command\r\n", "Unknown command caught");
		return sendCommand_("\r\n");
	}).catch(function (error) {
		QUnit.equal(error.message, "500 Unknown command\r\n");
		return sendCommand_("PASS chrome@example.com\r\n");
	}).catch(function (error) {
		QUnit.equal(error.message, "530 Not logged in\r\n", "PASS was not preceded by USER");
		return sendCommand_("USER anonymous\r\n");
	}).then(function (reply) {
		QUnit.equal(reply, "331 User name okay, need password\r\n");
		return sendCommand_("PASS chrome@example.com\r\n");
	}).then(function (reply) {
		QUnit.equal(reply, "230 User logged in, proceed\r\n");
		return sendCommand_("PASV\r\n");
	}).then(function (reply) {
		var n = reply.search("227 Entering passive mode");
		var re = /\d{1,3},\d{1,3},\d{1,3},\d{1,3},(\d{1,3}),(\d{1,3})/;
    var hostPort = re.exec(reply);
		QUnit.equal(n, 0, "passive mode reply returned");
		QUnit.ok(hostPort != null, "Host and port information sent");
		QUnit.start();
	}).catch(function (error) {
		console.log(error);
		QUnit.equal(error, "This test should have succeeded");
		QUnit.start();
	});
});

QUnit.test("sendCommand errors", function () {
	QUnit.stop();
	expect(1);
	var client = new FTPClient(MockSocket);
	var FTPInfo = client.test.FTPInfo,
		sendCommand_ = client.test.sendCommand_;
	// not connected, hence can't send commands!
	sendCommand_("USER anonymous\r\n").then(function (reply) {
		QUnit.equal(reply, "This should fail");
		QUnit.start();
	}).catch(function (error) {
		QUnit.equal(error.message, "Error code -2", "send error caught");
		QUnit.start();
	});
});

QUnit.test("parsePASV", function () {
	expect(8);
	var client = new FTPClient(MockSocket);
	var FTPInfo = client.test.FTPInfo, parsePASVReply_ = client.test.parsePASVReply_;
	var port = parsePASVReply_("227 Entering passive mode");
	QUnit.ok(port === null, "Invalid reply");
	
	port = parsePASVReply_("227 Entering passive mode 13,15");
	QUnit.ok(port === null, "invalid reply");
	
	port = parsePASVReply_("227 Entering passive mode 0,0,0,0,15,45");
	QUnit.equal(port, 15<<8 | 45, "valid port number received");
	
	port = parsePASVReply_("227 Entering passive mode (0,0,0,0,15,45)");
	QUnit.equal(port, 15<<8 | 45, "valid port number received");
	
	port = parsePASVReply_("227 Entering passive mode (0,0,0,0,15)");
	QUnit.ok(port === null, "invalid reply");
	
	port = parsePASVReply_("227 Entering passive mode 000,0,0,0,1500,45");
	QUnit.ok(port === null, "invalid reply");
	
	port = parsePASVReply_("227 Entering passive mode (0,0,0,0,300,45)");
	QUnit.ok(port === null, "invalid reply");
	
	port = parsePASVReply_("227 Entering passive mode (0,0,0,0,300,450)");
	QUnit.ok(port === null, "invalid reply");
});

QUnit.test("parseESPV_", function () {
	var client = new FTPClient(MockSocket);
	var FTPInfo = client.test.FTPInfo, parseEPSVReply_ = client.test.parseEPSVReply_;
	var port = parseEPSVReply_("227 Entering passive mode");
	QUnit.ok(port === null, "Invalid reply");
	
	port = parseEPSVReply_("227 Entering passive mode (13,15)");
	QUnit.ok(port === null, "invalid reply");
	
	port = parseEPSVReply_("227 Entering passive mode (||1315)");
	QUnit.ok(port === null, "invalid reply");
	
	port = parseEPSVReply_("227 Entering passive mode (|||1315)");
	QUnit.ok(port === null, "invalid reply");
	
	port = parseEPSVReply_("227 Entering passive mode (|||13152|)");
	QUnit.ok(port == 13152, "invalid reply");
	
	port = parseEPSVReply_("227 Entering passive mode |||13215|");
	QUnit.ok(port === null, "invalid reply");
	
	port = parseEPSVReply_("227 Entering passive mode (13152|)");
	QUnit.ok(port === null, "invalid reply");
	
	port = parseEPSVReply_("227 Entering passive mode 13215");
	QUnit.ok(port === null, "invalid reply");
});

QUnit.test("dataConnect", function () {
	QUnit.stop();
	expect(3);
	var client = new FTPClient(MockSocket);
	var FTPInfo = client.test.FTPInfo, dataConnect_ = client.test.dataConnect_;
	client.connect("cmu.edu", 21).then(function (reply){
		QUnit.equal(reply, "220 Service ready for new user\r\n");
		return dataConnect_();
	}).then(function (connectInfo) {
		QUnit.ok(connectInfo.socketId >= 0, "created data socket");
		QUnit.ok(connectInfo.port != null, "Got a data port from server");
		QUnit.start();
	}).catch(function (error) {
		QUnit.equal(error, "This test should have succeeded");
		QUnit.start();
  });
});

QUnit.test("login", function () {
	QUnit.stop();
	expect(2);
	var client = new FTPClient(MockSocket);
	var FTPInfo = client.test.FTPInfo;
	client.connect("cmu.edu", 21).then(function (reply) {
		QUnit.equal(reply, "220 Service ready for new user\r\n");
		return client.login();
	}).then(function (reply) {
		QUnit.equal(reply, "230 User logged in, proceed\r\n");
		QUnit.start();
	});
});

QUnit.test("noop", function (pathname) {
	QUnit.stop();
	expect(2);
	var client = new FTPClient(MockSocket), noop_ = client.test.noop_;
	client.connect("cmu.edu", 21).then(function (reply) {
		QUnit.equal(reply, "220 Service ready for new user\r\n");
		return noop_();
	}).then(function (reply) {
		QUnit.equal(reply, "220 OK\r\n");
		QUnit.start();
	}).catch(function (error) {
		QUnit.equal(reply, "This test should have suceeded");
		QUnit.start();
	});
});

QUnit.test("parsePWDReply_", function (pathname) {
	expect(10);
	var client = new FTPClient(MockSocket),
    parsePWDReply_ = client.test.parsePWDReply_;
	var dir = parsePWDReply_("227 \"/home/ftp/\"\r\n");
	QUnit.ok(dir == "/home/ftp/", "Parsed dir");

	dir = parsePWDReply_("227 \"/home/ftp/\r\n");
	QUnit.ok(dir === null, "invalid reply");
	
	dir = parsePWDReply_("227 \"/home/ftp\"");
	QUnit.ok(dir == "/home/ftp/", "parsed dir");
	
	dir = parsePWDReply_("227 /home/ftp/\r\n");
	QUnit.ok(dir == "/home/ftp/" , "parsed dir");

	dir = parsePWDReply_("227 /home/ftp/");
	QUnit.ok(dir === null, "invalid reply");

	dir = parsePWDReply_("227 \"/\"");
	QUnit.ok(dir == "/", "parsed dir");

	dir = parsePWDReply_("227 /home/ftp\r");
	QUnit.ok(dir == "/home/ftp/", "parsed dir");
	
	dir = parsePWDReply_("227 /home/ftp\n");
	QUnit.ok(dir == "/home/ftp/", "parsed dir");
	
	dir = parsePWDReply_("227\r\n");
	QUnit.ok(dir === null, "invalid reply");
	
	dir = parsePWDReply_("227 \r\n");
	QUnit.ok(dir === null, "invalid reply");
});

QUnit.test("getCurrentDir", function (pathname) {
	QUnit.stop();
	expect(2);
	var client = new FTPClient(MockSocket),
    getCurrentDir = client.getCurrentDir;
	client.connect("cmu.edu", 21).then(function (reply) {
    QUnit.equal(reply, "220 Service ready for new user\r\n");
		return getCurrentDir();
	}).then(function (reply) {
		QUnit.equal(reply, "/home/ftp/");
		QUnit.start();
	}).catch(function (error) {
		QUnit.equal(error, "This test should have suceeded");
		QUnit.start();
	});
});

QUnit.test("changeWorkingDir", function (pathname) {
	QUnit.stop();
	expect(2);
	var client = new FTPClient(MockSocket),
    changeWorkingDir = client.changeWorkingDir;
	client.connect("cmu.edu", 21).then(function (reply) {
		QUnit.equal(reply, "220 Service ready for new user\r\n");
		return changeWorkingDir("/home/ftpclient/");
	}).then(function (reply) {
    QUnit.equal(reply, "250 Directory changed\r\n");
		QUnit.start();
	}).catch(function (error) {
		QUnit.equal(reply, "This test should have suceeded");
		QUnit.start();
	});
});

QUnit.test("changeToParentDir", function (pathname) {
	QUnit.stop();
	expect(2);
  var client = new FTPClient(MockSocket),
    changeToParentDir = client.changeToParentDir;
	client.connect("cmu.edu", 21).then(function (reply) {
		QUnit.equal(reply, "220 Service ready for new user\r\n");
		return changeToParentDir("/home/ftpclient/");
	}).then(function (reply) {
		QUnit.equal(reply, "200 Directory changed\r\n");
		QUnit.start();
	}).catch(function (error) {
		QUnit.equal(reply, "This test should have suceeded");
		QUnit.start();
	});
});


